/*
 * @Author: Swance
 * @Date: 2024-02-18
 * @Description: 恢复撤销插件
 */
import { fabric } from 'fabric';
import { IEditor } from '@/types/global';
import Action from '@/utils/Action.ts';
import Stack from '@/utils/Stack.ts';
import { BoardSyncEvent, JsonOption } from '@/utils/Const.ts';

class HistoryPlugin {
  canvas: fabric.Canvas;
  editor: IEditor;
  // 临时保存中间状态和初始状态
  originObjects: any[];
  // 存储恢复操作的栈
  redoStack: Stack;
  // 存储撤回操作的栈
  undoStack: Stack;
  static pluginName = 'HistoryPlugin';
  static apis: string[] = ['undo', 'redo'];

  constructor(canvas: fabric.Canvas, editor: IEditor) {
    this.canvas = canvas;
    this.editor = editor;
    this.originObjects = [];
    this.redoStack = new Stack<Action>();
    this.undoStack = new Stack<Action>();
    this.init();
  }

  init() {
    const { canvas } = this;
    // 处理 modified 事件（添加文字元素 或 修改了某个元素的伸缩、放大和移动）
    canvas.on('object:modified', (e) => {
      const { target, action } = e;
      // 修改批量元素 情况
      if (!target) return;
      if (target.type === 'activeSelection') {
        console.log(e);
        const newObjectsJsons: any =
          this.editor.calcAbsolutePositionFromMultiple();
        const oldObjectsJsons: any[] = [];
        newObjectsJsons.forEach((item: any) => {
          // 多选的放大、拉伸和旋转出现问题，每一次多选框的拉伸数值都是基于现状计算的，而不是累加的，故单纯赋值是有问题的
          // 目前解决方案：多选框内元素的 zoomX 和 zoomY 会实时变化，把 zoomX 和 zoomY 分别赋值给 scaleX 和 scaleY
          // 旋转时 angle 累加
          item.scaleX = item.zoomX;
          item.scaleY = item.zoomY;
          if (action === 'rotate') {
            item.angle += target.angle;
            if (item.angle >= 360) {
              item.angle = item.angle - 360;
            }
          }
          const tmp = this.originObjects.find((item2: any) => {
            return item.id === item2.id;
          });
          if (tmp) {
            oldObjectsJsons.push(tmp);
          }
        });
        console.log('newObjectsJsons', newObjectsJsons);
        // const newObjects = target.getObjects();
        // // 获取 activeSelection 的变换矩阵，通过矩阵计算内里元素的准确位置
        // const matrix = target.calcTransformMatrix();
        // // 在多选状态下，对多选框内的元素重新计算绝对定位并保存
        // newObjects.forEach((item: any) => {
        //   const itemJson = item.toJSON([
        //     'id',
        //     'myType',
        //     'type',
        //     'lock',
        //     'erasable',
        //   ]);
        //
        //   const objectPosition = { x: item.left, y: item.top };
        //   const finalPosition = fabric.util.transformPoint(
        //     objectPosition,
        //     matrix,
        //   );
        //   itemJson.top = finalPosition.y;
        //   itemJson.left = finalPosition.x;
        //   // 针对批量修改操作类型手动赋值给多选框的元素，因为其不会随着多选框的修改而实时更新数据
        //   if (action === 'scaleX') {
        //     itemJson.scaleX = target.scaleX;
        //   } else if (action === 'scaleY') {
        //     itemJson.scaleY = target.scaleY;
        //   } else if (action === 'scale') {
        //     itemJson.scaleX = target.scaleX;
        //     itemJson.scaleY = target.scaleY;
        //   } else if (action === 'rotate') {
        //     itemJson.angle = target.angle;
        //   }
        //   newObjectsJsons.push(itemJson);
        //   const tmp = this.originObjects.find((item2: any) => {
        //     return item.id === item2.id;
        //   });
        //   if (tmp) {
        //     oldObjectsJsons.push(tmp);
        //   }
        // });
        this.editor.emit(BoardSyncEvent.UPDATE, {
          isNewAction: true,
          oldElements: [...oldObjectsJsons],
          newElements: [...newObjectsJsons],
        });
      } else {
        // 单选时获取初始状态
        const t = this.originObjects.find((item) => {
          return item.id === target.id;
        });
        if (!t && target.type === 'textbox') {
          this.editor.emit(BoardSyncEvent.ADD, {
            isNewAction: true,
            newElements: [target],
          });
        } else {
          const newJson = target.toJSON(JsonOption);
          this.editor.emit(BoardSyncEvent.UPDATE, {
            isNewAction: true,
            oldElements: [t],
            newElements: [newJson],
          });
        }
      }
    });
    this.editor.on('selected', ({ selected }) => {
      this.originObjects.length = 0;
      if (selected.length > 1) {
        const oldElementsJsons = this.editor.calcAbsolutePositionFromMultiple();
        this.originObjects = [...oldElementsJsons];
        // const activeSelection = canvas.getActiveObject();
        // // 获取 activeSelection 的变换矩阵，通过矩阵计算内里元素的准确位置
        // const matrix = activeSelection.calcTransformMatrix();
        // const allActiveObjects = activeSelection.getObjects();
        // allActiveObjects.forEach((item: any) => {
        //   const objectPosition = { x: item.left, y: item.top };
        //   const finalPosition = fabric.util.transformPoint(
        //     objectPosition,
        //     matrix,
        //   );
        //   const itemJson = item.toJSON([
        //     'id',
        //     'myType',
        //     'type',
        //     'lock',
        //     'erasable',
        //   ]);
        //   itemJson.top = finalPosition.y;
        //   itemJson.left = finalPosition.x;
        //   this.originObjects.push(itemJson);
        // });
      } else {
        selected.forEach((item: any) => {
          this.originObjects.push(item.toJSON(JsonOption));
        });
      }
    });
    this.editor.on('updateSelect', ({ selected }) => {
      this.originObjects.length = 0;
      if (selected.length > 1) return;
      selected.forEach((item: any) => {
        this.originObjects.push(item.toJSON(JsonOption));
      });
    });
    this.editor.on('deselected', () => {
      this.originObjects.length = 0;
    });

    // 监听用户交互产生的元素添加事件
    this.editor.on(BoardSyncEvent.ADD, (data: any) => {
      if (!this.redoStack.isEmpty() && data.isNewAction) {
        this.redoStack.clear();
      }
      if (data.newElements) {
        this.editor.addElementMap(data.newElements);
        const newElementsJson = data.newElements.map((item: any) => {
          return item.toJSON(JsonOption);
        });
        const addAction = new Action(
          BoardSyncEvent.ADD,
          data.isNewAction,
          [],
          [...newElementsJson],
        );
        this.pushActionIntoUndoStack(addAction);
      }
      this.editor.emit('canUndo', !this.undoStack.isEmpty());
      this.editor.emit('canRedo', !this.redoStack.isEmpty());
    });
    // 监听用户交互产生的元素删除事件
    this.editor.on(BoardSyncEvent.REMOVE, (data: any) => {
      if (!this.redoStack.isEmpty() && data.isNewAction) {
        this.redoStack.clear();
      }
      if (data.oldElements) {
        const oldElementsJson = data.oldElements.map((item: any) => {
          this.editor.removeElementMap(item);
          return item.toJSON(JsonOption);
        });
        const removeAction = new Action(
          BoardSyncEvent.REMOVE,
          data.isNewAction,
          [...oldElementsJson],
          [],
        );
        this.pushActionIntoUndoStack(removeAction);
      }
      this.editor.emit('canUndo', !this.undoStack.isEmpty());
      this.editor.emit('canRedo', !this.redoStack.isEmpty());
    });
    // 监听用户交互产生的元素修改事件
    this.editor.on(BoardSyncEvent.UPDATE, (data: any) => {
      if (!this.redoStack.isEmpty() && data.isNewAction) {
        this.redoStack.clear();
      }
      // 保存了新旧状态后，最终将最新状态作为中间状态存储起来
      this.originObjects = [...data.newElements];
      const updateAction = new Action(
        BoardSyncEvent.UPDATE,
        data.isNewAction,
        [...data.oldElements],
        [...data.newElements],
      );
      this.pushActionIntoUndoStack(updateAction);
      this.editor.emit('canUndo', !this.undoStack.isEmpty());
      this.editor.emit('canRedo', !this.redoStack.isEmpty());
    });
    // 监听用户交互产生的元素层级置顶事件
    this.editor.on(BoardSyncEvent.UP, (data: any) => {
      if (!this.redoStack.isEmpty() && data.isNewAction) {
        this.redoStack.clear();
      }
      if (data.oldElements) {
        const upAction = new Action(
          BoardSyncEvent.UP,
          data.isNewAction,
          [...data.oldElements],
          [],
        );
        this.pushActionIntoUndoStack(upAction);
        this.editor.emit('canUndo', !this.undoStack.isEmpty());
        this.editor.emit('canRedo', !this.redoStack.isEmpty());
      }
    });
    // 监听用户交互产生的元素层级置底事件
    this.editor.on(BoardSyncEvent.DOWN, (data: any) => {
      if (!this.redoStack.isEmpty() && data.isNewAction) {
        this.redoStack.clear();
      }
      if (data.oldElements) {
        const downAction = new Action(
          BoardSyncEvent.DOWN,
          data.isNewAction,
          [...data.oldElements],
          [],
        );
        this.pushActionIntoUndoStack(downAction);
        this.editor.emit('canUndo', !this.undoStack.isEmpty());
        this.editor.emit('canRedo', !this.redoStack.isEmpty());
      }
    });
  }

  // 撤回操作
  undo() {
    if (this.undoStack.isEmpty()) return;
    this.canvas.discardActiveObject();
    // 从 undo 栈中弹出最新的 action 对象
    const action = this.undoStack.pop();
    // 深拷贝一份 action 对象
    const copyAction = JSON.parse(JSON.stringify(action));
    copyAction.convert = action.convert;
    copyAction.setNewFlag = action.setNewFlag;
    copyAction.getNewFlag = action.getNewFlag;
    // 将 action 存入 redo 栈中
    this.redoStack.push(action);
    // 将拷贝多出的 copyAction 转换为逆操作后执行
    copyAction.convert();
    // 用户 undo 时，也要把逆操作同步给服务端
    this.executeAction(copyAction, 'undo');
    this.editor.emit(BoardSyncEvent.Sync, copyAction);
    this.editor.emit('canUndo', !this.undoStack.isEmpty());
    this.editor.emit('canRedo', !this.redoStack.isEmpty());
  }

  // 恢复操作
  redo() {
    if (this.redoStack.isEmpty()) return;
    this.canvas.discardActiveObject();
    // 从 redo 栈中弹出 action
    const action = this.redoStack.pop();
    // 修改 action 中新旧变更标识
    action.setNewFlag(false);
    // 执行 action
    this.executeAction(action, 'redo');
    this.editor.emit('canUndo', !this.undoStack.isEmpty());
    this.editor.emit('canRedo', !this.redoStack.isEmpty());
  }

  // action 进入 undo 栈时同步操作给服务端
  pushActionIntoUndoStack(action: Action) {
    this.undoStack.push(action);
    this.editor.emit(BoardSyncEvent.Sync, action);
  }

  // 执行 action
  executeAction(action: Action, option?: string) {
    if (!action) return;

    switch (action.type) {
      // 执行 删除 操作
      case BoardSyncEvent.REMOVE: {
        if (!action.oldElements.length) return;
        const oldElementsObjects = action.oldElements.map((item: any) => {
          return this.editor.getElementById(item.id);
        });
        this.canvas.remove(...oldElementsObjects);
        this.editor.removeElementMap(oldElementsObjects);
        if (option === 'redo') {
          this.editor.emit(BoardSyncEvent.REMOVE, {
            isNewAction: false,
            oldElements: [...oldElementsObjects],
          });
        }
        break;
      }
      // 执行 添加 操作
      case BoardSyncEvent.ADD: {
        const that = this;
        if (!action.newElements.length) return;
        fabric.util.enlivenObjects(
          action.newElements,
          function (objects: any[]) {
            that.canvas.add(...objects);
            that.editor.addElementMap(objects);
            if (option === 'redo') {
              that.editor.emit(BoardSyncEvent.ADD, {
                isNewAction: false,
                newElements: [...objects],
              });
            }
          },
          'fabric',
        );
        break;
      }
      // 执行 修改 操作
      case BoardSyncEvent.UPDATE: {
        if (!action.oldElements.length && !action.newElements.length) return;
        // 撤回修改时 删除新状态，添加旧状态
        // if (option === 'undo') {
        //   // 删除新状态
        //   const newElementsObjects = action.newElements.map((item: any) => {
        //     return this.editor.getElementById(item.id);
        //   });
        //   this.canvas.remove(...newElementsObjects);
        //   // this.editor.removeElementMap(newElementsObjects);
        //   // 添加旧状态
        //   const that = this;
        //   fabric.util.enlivenObjects(
        //     action.oldElements,
        //     function (objects: any[]) {
        //       that.canvas.add(...objects);
        //       that.editor.addElementMap(objects);
        //     },
        //     'fabric',
        //   );
        //   // 恢复修改时 添加新状态，删除旧状态
        // } else {

        // 删除旧状态
        const oldElementsObjects = action.oldElements.map((item: any) => {
          return this.editor.getElementById(item.id);
        });
        this.canvas.remove(...oldElementsObjects);
        this.editor.removeElementMap(oldElementsObjects);
        // 添加新状态
        const that = this;
        fabric.util.enlivenObjects(
          action.newElements,
          function (objects: any[]) {
            that.canvas.add(...objects);
            that.editor.addElementMap(objects);
          },
          'fabric',
        );
        if (option === 'redo') {
          that.editor.emit(BoardSyncEvent.UPDATE, {
            isNewAction: false,
            oldElements: [...action.oldElements],
            newElements: [...action.newElements],
          });
        }
        // }
        break;
      }
      // 执行 置于顶层 操作
      case BoardSyncEvent.UP: {
        const that = this;
        if (!action.oldElements.length) return;
        const oldElementsObjects = action.oldElements.map((item: any) => {
          return this.editor.getElementById(item.id);
        });
        this.canvas.remove(...oldElementsObjects);
        // this.editor.removeElementMap(oldElementsObjects);
        fabric.util.enlivenObjects(
          action.oldElements,
          function (objects: any[]) {
            that.canvas.add(...objects);
            objects.forEach((item) => {
              that.canvas.bringToFront(item);
            });
            that.editor.addElementMap(objects);
          },
          'fabric',
        );
        if (option === 'redo') {
          that.editor.emit(BoardSyncEvent.UP, {
            isNewAction: false,
            oldElements: [...action.oldElements],
          });
        }
        break;
      }
      case BoardSyncEvent.DOWN: {
        const that = this;
        if (!action.oldElements.length) return;
        const oldElementsObjects = action.oldElements.map((item: any) => {
          return this.editor.getElementById(item.id);
        });
        this.canvas.remove(...oldElementsObjects);
        // this.editor.removeElementMap(oldElementsObjects);
        fabric.util.enlivenObjects(
          action.oldElements,
          function (objects: any[]) {
            that.canvas.add(...objects);
            objects.forEach((item) => {
              that.canvas.sendToBack(item);
            });
            that.editor.addElementMap(objects);
          },
          'fabric',
        );
        if (option === 'redo') {
          that.editor.emit(BoardSyncEvent.DOWN, {
            isNewAction: false,
            oldElements: [...action.oldElements],
          });
        }
        break;
      }
    }
  }
}
export default HistoryPlugin;
